import tkinter as tk
from   tkinter import ttk, filedialog, messagebox
from   PIL     import Image, ImageTk
from   qrcode  import QRCode
import os
import re
import io
import json
import time
import requests
import threading
import subprocess

class VideoDownloader:
    data_block = 1024 * 100
    current_info = {'state': False, 'info': ''}
    cookies = None
    headers = {
        "Referer": "https://www.bilibili.com/",
        "Origin": "https://www.bilibili.com/",
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
    }
    
    def __init__(self):
        if os.path.isfile('cookies.ini'):
            with open('cookies.ini', 'r') as f:
                self.cookies = requests.utils.cookiejar_from_dict(eval(f.read()))
        else:
            self.cookies = requests.utils.cookiejar_from_dict({})

    def getVideoInfo(self, url: str) -> dict:
        regist_filename = []
        def get_filename(name:str) -> str:
            name = safe_filename(name);extra_name:int = 0
            while  name in regist_filename:extra_name += 1
            return name + (f'({extra_name})' if extra_name > 0 else '')
        def safe_filename(filename: str) -> str:
            return re.sub(r'[\\/:*?"<>|]', '', filename)
        def get_p(url:str) -> int:
            try   : return int(re.search(r'(?:[?&]p=)([^&]*)', url).group(1) if re.search(r'[?&]p=', url) else '1')
            except: return 1
        def url_parse(url: str) -> str | None:
            try:
                response = requests.head(url, allow_redirects=True)
                if 'location' in response.headers:
                    return url_parse(response.headers['location'])
                else:
                    return re.findall(r'http.*?(?=\?|$)', response.url)[0] + '?p=' + str(get_p(response.url))
            except:
                return None
        try:
            app.parse_btn.config(state='disabled')
            app.video_btn.config(state='disabled')
            app.audio_btn.config(state='disabled')
            app.show_list_btn.config(state='disabled')
            app.url_entry.delete(0, 'end')
            app.url_entry.insert(0, re.findall(r'http.*?(?=\?|$)', url)[0] + '?p=' + str(get_p(url)))
            url = url_parse(re.findall(r'http.*?(?=\?|$)', url)[0] + '?p=' + str(get_p(url))) if 'http' in url else url
            if not "https://www.bilibili.com/video/BV" in url:
                app.parse_btn.config(state='normal')
                return {'state': False, 'info': "请输入正确的视频链接"}
            html = requests.get(url=url, headers=self.headers, cookies=self.cookies).text
            title = re.findall('title="(.*?)"', html)[0]
            json_data = json.loads(re.findall('window.__playinfo__=(.*?)</script>', html)[0])
            video_dpi = (json_data['data']['dash']['video'][0]['width'], json_data['data']['dash']['video'][0]['height'])
            video_url = json_data['data']['dash']['video'][0]['baseUrl']
            audio_url = json_data['data']['dash']['audio'][0]['baseUrl']
            video_size = int(requests.get(url=video_url, headers=self.headers, cookies=self.cookies, stream=True).headers.get('Content-Length'))
            audio_size = int(requests.get(url=audio_url, headers=self.headers, cookies=self.cookies, stream=True).headers.get('Content-Length'))
            InitialState = json.loads(re.findall('window.__INITIAL_STATE__=(.*?);', html)[0])
            if 'ugc_season' in InitialState['videoData'] or len(InitialState["availableVideoList"][0]['list']) > 1:
                ugc_season_title = InitialState['videoData']['ugc_season']['title'] if 'ugc_season' in InitialState['videoData'] else ''
                app.tree_list.checkbox_states = {}
                for i in app.tree_list.get_children(): app.tree_list.delete(i)
                for item in InitialState["availableVideoList"]:
                    app.tree_list.insert('', 'end', iid=item['bvid'], text=item['title'], open=True, values=(app.tree_list.checkbox_list[0],ugc_season_title,get_filename(item['title']),))
                    if len(item['list']) > 1:
                        for sub_item in item['list']:
                            app.tree_list.insert(item['bvid'], 'end', iid=item['bvid']+'?p='+str(sub_item['p']), text=sub_item['title'], values=(app.tree_list.checkbox_list[0],ugc_season_title+'/'+safe_filename(item['title']),safe_filename(sub_item['title']),))
                    else:   regist_filename.append(get_filename(item['title']))
                    if 'ugc_season' not in InitialState['videoData']:break
                app.show_list_btn.config(state='normal')
            # print(InitialState["availableVideoList"] # 所有合集信息
            # print(InitialState["availableVideoList"][index]['title']
            # print(InitialState["availableVideoList"][index]['bvid']
            # print(InitialState["videoData"]['ugc_season']['title'])  # 合集标题
            # print(InitialState["availableVideoList"][0]['title'])    # 分P 标题
            # print(InitialState["availableVideoList"][0]['list'][index]['title']) # 各个分P标题 (index从0开始到len(InitialState["availableVideoList"][0]['list'])-1)
        except Exception as e:
            app.parse_btn.config(state='normal')
            e = str(e) if not 'range'    in str(e) else '链接错误，请检查链接是否正确'
            return {'state': False, 'info': str(e)}
        app.parse_btn.config(state='normal')
        app.video_btn.config(state='normal')
        app.audio_btn.config(state='normal')
        self.current_info = {  'url': (video_url, audio_url), 'size': (video_size, audio_size), 'dpi': video_dpi, 'title': title}
        return {'state': True, 'url': (video_url, audio_url), 'size': (video_size, audio_size), 'dpi': video_dpi, 'title': title}
    
    def download(self, path: str = None, file_name: str = None, info:dict|str = None, only_audio: bool = False, progressFunc: callable = None, taskID = None):
        def system(cmd: str):
            return subprocess.Popen(cmd, shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).wait()
        
        def safe_filename(filename: str) -> str:
            return re.sub(r'[\\/:*?"<>|]', '', filename)
        
        app.tasks_tree.set(taskID, "status", "准备中")
        
        if isinstance(info, str):
            try:
                html = requests.get(url=info, headers=self.headers, cookies=self.cookies).text
                title = re.findall('title="(.*?)"', html)[0]
                json_data = json.loads(re.findall('window.__playinfo__=(.*?)</script>', html)[0])
                video_url = json_data['data']['dash']['video'][0]['baseUrl']
                audio_url = json_data['data']['dash']['audio'][0]['baseUrl']
                video_size = int(requests.get(url=video_url, headers=self.headers, cookies=self.cookies, stream=True).headers.get('Content-Length'))
                audio_size = int(requests.get(url=audio_url, headers=self.headers, cookies=self.cookies, stream=True).headers.get('Content-Length'))
                info = {'url': (video_url, audio_url), 'size': (video_size, audio_size), 'title': title}
            except Exception as e:return

        file_name = safe_filename(info['title']) if not file_name else safe_filename(file_name)
        
        if not os.path.exists(path):
            os.makedirs(path)
        
        if not only_audio:
            video_url  , audio_url  = info['url']
            video_size , audio_size = info['size']
            video_file = os.path.join(path, file_name + '[00].mp4')
            audio_file = os.path.join(path, file_name + '[01].aac')
            if os.path.isdir(video_file) or os.path.isdir(audio_file):
                if  progressFunc:
                    progressFunc(total=video_size + audio_size, current=0, taskID=taskID, success=False, info='下载失败：转存文件与文件夹重名')
                return
            try:
                if os.path.isfile(video_file):os.remove(video_file)
                if os.path.isfile(audio_file):os.remove(audio_file)
                if  progressFunc:
                    progressFunc(total=video_size + audio_size, current=0, taskID=taskID)
                
                with open(video_file, 'wb') as video_stream, open(audio_file, 'wb') as audio_stream:
                    video_response = requests.get(url=video_url, headers=self.headers, cookies=self.cookies, stream=True, timeout=(10, 20))
                    for data in video_response.iter_content(self.data_block):
                        video_stream.write(data)
                        if not app.tasks_tree.exists(taskID):
                            try:
                                video_stream.close()
                                audio_stream.close()
                                os.remove(video_file)
                                os.remove(audio_file)
                            except:pass
                            return
                        if  progressFunc:
                            progressFunc(total=video_size + audio_size, current=video_stream.tell(), taskID=taskID)
                        try:
                            while app.tasks_tree.item(taskID, "values")[3] == '暂停中':
                                time.sleep(0.5)
                        except:
                            if not app.tasks_tree.exists(taskID):
                                try:
                                    video_stream.close()
                                    audio_stream.close()
                                    os.remove(video_file)
                                    os.remove(audio_file)
                                except:pass
                                return
                    audio_response = requests.get(url=audio_url, headers=self.headers, cookies=self.cookies, stream=True, timeout=(10, 20))
                    for data in audio_response.iter_content(self.data_block):
                        audio_stream.write(data)
                        if not app.tasks_tree.exists(taskID):
                            try:
                                video_stream.close()
                                audio_stream.close()
                                os.remove(video_file)
                                os.remove(audio_file)
                            except:pass
                            return
                        if  progressFunc:
                            progressFunc(total=video_size + audio_size, current=video_stream.tell() + audio_stream.tell(), taskID=taskID)
                        try:
                            while app.tasks_tree.item(taskID, "values")[3] == '暂停中':
                                time.sleep(0.5)
                        except:
                            if not app.tasks_tree.exists(taskID):
                                try:
                                    video_stream.close()
                                    audio_stream.close()
                                    os.remove(video_file)
                                    os.remove(audio_file)
                                except:pass
                                return
                
                if  progressFunc:
                    progressFunc(total=video_size + audio_size, current=video_size + audio_size, taskID=taskID, success=False)
                if  os.path.isfile(os.path.join(path, file_name) + '.mp4'):os.remove(os.path.join(path, file_name) + '.mp4')
                system(f"ffmpeg -i \"{video_file}\" -i \"{audio_file}\" -c copy -metadata description=\"\" \"{os.path.join(path, file_name)}.mp4\" -y")
                os.remove(video_file)
                os.remove(audio_file)
                if  progressFunc:
                    progressFunc(total=video_size + audio_size, current=video_size + audio_size, taskID=taskID, success=True)
            except Exception as e:
                if  progressFunc:
                    progressFunc(total=video_size + audio_size, current=video_size + audio_size, taskID=taskID, success=False, info=str(e))
        else:
            audio_url  = info['url'] [1]
            audio_size = info['size'][1]
            audio_file = os.path.join(path, file_name + '.aac')
            if  os.path.isdir(audio_file):
                if  progressFunc:
                    progressFunc(total=audio_size, current=0, taskID=taskID, success=False, info='下载失败：转存文件与文件夹重名')
                return
            try:
                if os.path.isfile(audio_file):os.remove(audio_file)
                if  progressFunc:
                    progressFunc(total=audio_size, current=0, taskID=taskID)
                
                with open(audio_file, 'wb') as audio_stream:
                    audio_response = requests.get(url=audio_url, headers=self.headers, cookies=self.cookies, stream=True, timeout=(10, 20))
                    for data in audio_response.iter_content(self.data_block):
                        if not app.tasks_tree.exists(taskID):
                            try:
                                audio_stream.close()
                                os.remove(audio_file)
                            except:pass
                            return
                        audio_stream.write(data)
                        if progressFunc:
                            progressFunc(total=audio_size, current=audio_stream.tell(), taskID=taskID)
                        try:
                            while app.tasks_tree.item(taskID, "values")[3] == '暂停中':
                                time.sleep(0.5)
                        except:
                            if not app.tasks_tree.exists(taskID):
                                try:
                                    audio_stream.close()
                                    os.remove(audio_file)
                                except:pass
                                return
                
                if  progressFunc:
                    progressFunc(total=audio_size, current=audio_size, taskID=taskID, success=False)
                if  os.path.isfile(os.path.join(path, file_name) + '.mp3'):os.remove(os.path.join(path, file_name) + '.mp3')
                system(f"ffmpeg -i \"{audio_file}\" -c mp3 -metadata description=\"\" \"{os.path.join(path, file_name)}.mp3\" -y")
                os.remove(audio_file)
                if  progressFunc:
                    progressFunc(total=audio_size, current=audio_size, taskID=taskID, success=True)
            except Exception as e:
                if  progressFunc:
                    progressFunc(total=audio_size, current=audio_size, taskID=taskID, success=False, info=str(e))

class CheckboxTreeview(ttk.Treeview):
    checkbox_list = '🔲🟩✅'
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        
        # 设置列
        self["columns"] = ("checkbox","path","name",)
        self.column("#0", width=500, minwidth=500)
        self.column("checkbox", width=20, minwidth=20, anchor="center")
        # 隐藏checkbox列
        self.column("path", width=0, minwidth=0, stretch=False)
        self.column("name", width=0, minwidth=0, stretch=False)
        
        # 设置列标题
        self.heading("#0", text="视频标题", anchor="center")
        self.heading("checkbox", text="选择", anchor="center")
        
        # 绑定点击事件
        self.bind("<Button-1>", self.on_click)
        
        # 存储复选框状态
        self.checkbox_states = {}
    
    def insert(self, parent, index, iid=None, **kw):
        # 插入项目时添加复选框
        item = super().insert(parent, index, iid=iid, **kw)
        self.set(item, "checkbox", self.checkbox_list[0])
        self.checkbox_states[item] = False
        return item
    
    def on_click(self, event):
        # 获取点击的区域
        region = self.identify_region(event.x, event.y)
        if region == "cell":
            # 获取点击的项目和列
            column = self.identify_column(event.x)
            item = self.identify_row(event.y)
            
            if column == "#1":  # 复选框列
                # 切换复选框状态
                current_state = self.checkbox_states[item]
                new_state = not current_state
                self.checkbox_states[item] = new_state
                
                # 更新复选框显示
                self.set(item, "checkbox", self.checkbox_list[2] if new_state else self.checkbox_list[0])
                
                # 如果有子项目，同步选择状态
                if self.get_children(item):
                    self.sync_children_state(item, new_state)
                
                # 如果有父项目，检查是否需要更新父项目状态
                parent = self.parent(item)
                if parent:
                    self.update_parent_state(parent)

    def sync_children_state(self, parent, state):
        # 同步子项目的选择状态
        for child in self.get_children(parent):
            self.checkbox_states[child] = state
            self.set(child, "checkbox", self.checkbox_list[2] if state else self.checkbox_list[0])
            
            # 递归同步子项目的子项目
            if self.get_children(child):
                self.sync_children_state(child, state)

    def update_parent_state(self, parent):
        # 更新父项目的选择状态
        children = self.get_children(parent)
        if not children:
            return
            
        # 检查所有子项目的状态
        all_checked = all(self.checkbox_states[child] for child in children)
        any_checked = any(self.checkbox_states[child] for child in children)
        
        # 设置父项目的状态
        if all_checked:
            self.checkbox_states[parent] = True
            self.set(parent, "checkbox", self.checkbox_list[2])
        elif any_checked:
            self.checkbox_states[parent] = False  # 部分选中状态
            self.set(parent, "checkbox", self.checkbox_list[1])
        else:
            self.checkbox_states[parent] = False
            self.set(parent, "checkbox", self.checkbox_list[0])
        
        # 递归更新父项目的父项目
        grandparent = self.parent(parent)
        if grandparent:
            self.update_parent_state(grandparent)

class CheckLogin(threading.Thread):
    def __init__(self, qrcode_key, session, update_callback):
        threading.Thread.__init__(self)
        self.qrcode_key = qrcode_key
        self.session = session
        self.update_callback = update_callback
        self.__run__ = True
        
    def destory(self):
        self.__run__ = False
        
    def run(self):
        check_login_url = f'https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key={self.qrcode_key}&source=main-fe-header'
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0',
            'Referer': 'https://www.bilibili.com/',
            'Origin': 'https://www.bilibili.com'
        }
        
        while self.__run__:
            try:
                data = self.session.get(url=check_login_url, headers=headers).json()
                if data['data']['code'] == 0:
                    # 登录成功
                    self.session.get('https://www.bilibili.com/', headers=headers)
                    with open('cookies.ini', 'w') as c: 
                        c.write(str(self.session.cookies.get_dict()))
                    self.update_callback(True, "登录成功！")
                    break
                elif data['data']['code'] == 86038:
                    # 二维码已过期
                    self.update_callback(False, "二维码已过期，请刷新二维码")
                    break
                elif data['data']['code'] == 86090:
                    # 二维码已扫描未确认
                    self.update_callback(None, "二维码已扫描，请在手机上确认登录")
                elif data['data']['code'] == 86101:
                    # 未扫描
                    pass
            except Exception as e:
                self.update_callback(False, f"登录检查错误: {str(e)}")
                break
            time.sleep(0.5)

class BiliBiliDownloader:
    tasks_CMID = 0
    tasks_tree = None
    tasks_thread = {}
    def __init__(self, root):
        self.root     = root
        screen_width  = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()

        x = int(max(0, (screen_width - 1000) // 4))
        y = int(max(0, (screen_height - 600) // 4))

        self.root.title("Open Bilibili Downloader")
        self.root.geometry("1000x600+{}+{}".format(x, y))
        self.root.minsize(1000, 600)
        self.downloader = VideoDownloader()

        self.setup_dark_theme()
        self.setup_styles()
        self.setup_layout()
        self.setup_download_page()
        self.setup_tasks_page()
        self.setup_login_page()
        self.setup_list_page()
        self.show_download_page()
        self.stats_update()
        self.download_manager()
    
    def setup_dark_theme(self):
        self.bg_color = "#1a1b26"
        self.sidebar_color = "#16161e"
        self.card_color = "#24283b"
        self.text_color = "#a9b1d6"
        self.accent_color = "#7aa2f7"
        self.success_color = "#9ece6a"
        self.warning_color = "#e0af68"
        self.error_color = "#f7768e"
        self.root.configure(bg=self.bg_color)
    
    def setup_styles(self):
        style = ttk.Style()
        style.theme_use('clam')
        
        style.configure('.', background=self.bg_color, foreground=self.text_color)
        style.configure('TFrame', background=self.bg_color)
        style.configure('TLabel', background=self.bg_color, foreground=self.text_color)
        style.configure('TEntry', fieldbackground=self.card_color, foreground=self.text_color, 
                       borderwidth=1, relief='flat', padding=5)
        style.configure('TButton', background=self.card_color, foreground=self.text_color,
                       borderwidth=1, relief='flat', padding=(10, 5))
        style.configure('TLabelframe', background=self.bg_color, foreground=self.accent_color)
        style.configure('TLabelframe.Label', background=self.bg_color, foreground=self.accent_color)
        
        style.configure('Nav.TButton', font=('Microsoft YaHei', 10), padding=10, 
                       background=self.sidebar_color, foreground=self.text_color)
        style.configure('Title.TLabel', font=('Microsoft YaHei', 12, 'bold'), foreground=self.accent_color)
        style.configure('Normal.TLabel', font=('Microsoft YaHei', 9))
        style.configure('Normal.TEntry', font=('Microsoft YaHei', 9), fieldbackground="#2a2e3f", 
                       bordercolor="#414868", focuscolor="#414868")
        style.configure('Action.TButton', font=('Microsoft YaHei', 10), background=self.accent_color, 
                       foreground='white', borderwidth=0, focuscolor=self.accent_color)
        style.configure('Secondary.TButton', font=('Microsoft YaHei', 9), background="#414868", 
                       foreground=self.text_color, borderwidth=0)
        style.configure('User.TLabel', font=('Microsoft YaHei', 10), foreground=self.text_color)
        
        style.configure('Disabled.TButton', font=('Microsoft YaHei', 10), background="#343b58", 
                       foreground="#565f89", borderwidth=0)
        
        style.configure('Treeview', 
                       background=self.card_color,
                       foreground=self.text_color,
                       fieldbackground=self.card_color,
                       borderwidth=0,
                       rowheight=25)
        style.map('Treeview', background=[('selected', self.accent_color)])
        style.configure('Treeview.Heading', 
                       background=self.sidebar_color,
                       foreground=self.text_color,
                       relief='flat',
                       font=('Microsoft YaHei', 9, 'bold'))
        
        style.configure('TNotebook', 
                       background=self.bg_color,
                       tabmargins=[0, 0, 0, 0])
        style.configure('TNotebook.Tab', 
                       background=self.sidebar_color,
                       foreground=self.text_color,
                       padding=[15, 5],
                       font=('Microsoft YaHei', 10),
                       focuscolor=self.bg_color)
        style.map('TNotebook.Tab', 
                 background=[('selected', self.bg_color), ('active', '#2a2e3f')],
                 foreground=[('selected', self.accent_color), ('active', self.accent_color)])
        
        style.map('Action.TButton',
                 background=[('active', '#658cbf'), ('pressed', '#5a7fb3')])
        style.map('Secondary.TButton',
                 background=[('active', '#51587a'), ('pressed', '#484e6d')])
        style.map('TButton',
                 background=[('active', '#343b58'), ('pressed', '#2d334d')])
        style.map('Nav.TButton',
                 background=[('active', '#1f2233'), ('pressed', '#191c2a')])
        
        # 垂直滚动条基础样式配置
        style.configure("TScrollbar",
                        background="#1E1E1E",      # 滑块颜色 - 浅灰色
                        troughcolor="#1E1E1E",     # 滑槽颜色 - 接近黑色
                        bordercolor="#3E3E42",     # 边框颜色 - 中等灰色
                        arrowcolor="#FCFCFC",      # 箭头颜色 - 白色
                        gripcount=0)                 # 移除默认握把图案

        # 垂直滚动条动态效果配置
        style.map("TScrollbar",
                background=[('active', '#3E3E42'),    # 悬停时中等灰色
                            ('pressed', '#007ACC'),   # 按下时蓝色
                            ('disabled', '#505050')], # 禁用时深灰色
                arrowcolor=[('active', '#FFFFFF'),    # 悬停时白色
                            ('pressed', '#FFFFFF'),   # 按下时白色
                            ('disabled', '#707070')], # 禁用时灰色
                troughcolor=[('active', '#252526')])  # 悬停时滑槽稍亮
    
    def setup_layout(self):
        self.main_paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
        self.main_paned.pack(fill=tk.BOTH, expand=True)
        
        style = ttk.Style()
        style.configure('Sidebar.TFrame', background=self.sidebar_color)
        self.nav_frame = ttk.Frame(self.main_paned, width=200, style='Sidebar.TFrame')
        self.nav_frame.pack_propagate(False)
        self.main_paned.add(self.nav_frame, weight=0)
        
        self.content_frame = ttk.Frame(self.main_paned, style='TFrame')
        self.main_paned.add(self.content_frame, weight=1)
        
        self.notebook = ttk.Notebook(self.content_frame, style='TNotebook')
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        self.download_page = ttk.Frame(self.notebook, style='TFrame')
        self.notebook.add(self.download_page, text="下载")
        
        self.tasks_page = ttk.Frame(self.notebook, style='TFrame')
        self.notebook.add(self.tasks_page, text="任务")

        self.login_page = ttk.Frame(self.notebook, style='TFrame')
        self.notebook.add(self.login_page, text="登录")

        self.list_page = ttk.Frame(self.notebook, style='TFrame')
        self.notebook.add(self.list_page, text="合集列表")
        
        self.notebook.tab(0, state="hidden")
        self.notebook.tab(1, state="hidden")
        self.notebook.tab(2, state="hidden")
        self.notebook.tab(3, state="hidden")
        
        self.setup_navigation()
    
    def setup_navigation(self):
        self.load_user_info()
        
        nav_buttons_frame = ttk.Frame(self.nav_frame, style='Sidebar.TFrame')
        nav_buttons_frame.pack(fill=tk.X, padx=10, pady=20)
        
        download_btn = ttk.Button(
            nav_buttons_frame, 
            text="📥 下载页面", 
            command=self.show_download_page,
            style='Nav.TButton'
        )
        download_btn.pack(fill=tk.X, pady=(0, 5))
        
        tasks_btn = ttk.Button(
            nav_buttons_frame, 
            text="📋 任务列表", 
            command=self.show_tasks_page,
            style='Nav.TButton'
        )
        tasks_btn.pack(fill=tk.X, pady=5)

        login_btn = ttk.Button(
            nav_buttons_frame, 
            text="👤 账户登录", 
            command=self.show_login_page,
            style='Nav.TButton'
        )
        login_btn.pack(fill=tk.X, pady=5)
        
        filler_frame = ttk.Frame(self.nav_frame, style='Sidebar.TFrame')
        filler_frame.pack(fill=tk.BOTH, expand=True)
        
        stats_frame = ttk.Frame(self.nav_frame, style='Sidebar.TFrame')
        stats_frame.pack(fill=tk.X, padx=15, pady=10, side=tk.BOTTOM)
        
        total_tasks = len(self.tasks_tree.get_children()) if self.tasks_tree else 0
        completed_tasks = sum(1 for item in self.tasks_tree.get_children() 
                            if self.tasks_tree.item(item, "values")[3] == "已完成") if self.tasks_tree else 0
        
        stats_text = f"总任务: {total_tasks}\n已完成: {completed_tasks}"
        self.stats_label = tk.Label(stats_frame, text=stats_text, 
                             fg=self.text_color, bg=self.sidebar_color,
                             font=('Microsoft YaHei', 9), justify=tk.LEFT)
        self.stats_label.pack(anchor=tk.W)
    
    def load_user_info(self):
        try:
            user_frame = ttk.Frame(self.nav_frame, style='Sidebar.TFrame')
            user_frame.pack(fill=tk.X, pady=20, padx=10, side=tk.TOP)
            name = "未登录"
            
            try:
                html = requests.get('https://api.bilibili.com/x/web-interface/nav', 
                                   headers=self.downloader.headers, 
                                   cookies=self.downloader.cookies).json()
                if html['code'] == 0:
                    name = html['data']['uname']
                    avatar_url = html['data']['face']
                    try:
                        response = requests.get(avatar_url, timeout=5)
                        img_data = response.content
                    except:pass
            except:pass
            
            try:
                image = Image.open(io.BytesIO(img_data))
                image = image.resize((80, 80), Image.Resampling.LANCZOS)
                avatar_img = ImageTk.PhotoImage(image)
            except:
                image = Image.new('RGBA', (80, 80), (0, 0, 0, 0))
                avatar_img = ImageTk.PhotoImage(image)
            
            self.avatar_label = tk.Label(user_frame, image=avatar_img, bg=self.sidebar_color)
            self.avatar_label.image = avatar_img
            self.avatar_label.pack(pady=(0, 10))
            
            self.user_name_label = tk.Label(user_frame, text=name, 
                                     fg=self.text_color, bg=self.sidebar_color,
                                     font=('Microsoft YaHei', 12, 'bold'))
            self.user_name_label.pack(pady=(0, 5))
            
            status = "已登录" if name != "未登录" else "未登录"
            self.status_label = tk.Label(user_frame, text=status, 
                                  fg=self.accent_color if name != "未登录" else self.warning_color, 
                                  bg=self.sidebar_color,
                                  font=('Microsoft YaHei', 9))
            self.status_label.pack()
            
            separator = ttk.Separator(self.nav_frame, orient='horizontal')
            separator.pack(fill=tk.X, padx=10, pady=10, side=tk.TOP)
            
        except Exception as e:
            print(f"加载用户信息时出错: {e}")

    def update_user_info(self):
        try:
            name = "未登录"
            
            if os.path.isfile('cookies.ini'):
                with open('cookies.ini', 'r') as f:
                    self.downloader.cookies = requests.utils.cookiejar_from_dict(eval(f.read()))
            else:
                self.downloader.cookies = requests.utils.cookiejar_from_dict({})

            try:
                html = requests.get('https://api.bilibili.com/x/web-interface/nav', 
                                   headers=self.downloader.headers, 
                                   cookies=self.downloader.cookies).json()
                if html['code'] == 0:
                    name = html['data']['uname']
                    avatar_url = html['data']['face']
                    try:
                        response = requests.get(avatar_url, timeout=5)
                        img_data = response.content
                    except:pass
            except:pass
            
            try:
                image = Image.open(io.BytesIO(img_data))
                image = image.resize((80, 80), Image.Resampling.LANCZOS)
                avatar_img = ImageTk.PhotoImage(image)
            except:
                image = Image.new('RGBA', (80, 80), (0, 0, 0, 0))
                avatar_img = ImageTk.PhotoImage(image)
            
            self.avatar_label.config(image = avatar_img)
            self.avatar_label.image = avatar_img
            
            self.user_name_label.config(text=name)
            
            status = "已登录" if name != "未登录" else "未登录"
            self.status_label.config(text=status, fg=self.accent_color if name != "未登录" else self.warning_color)
            
        except Exception as e:
            print(f"加载用户信息时出错: {e}")
    
    def setup_download_page(self):
        card_frame = tk.Frame(self.download_page, bg=self.card_color, bd=0, relief='flat')
        card_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        title_label = tk.Label(
            card_frame, 
            text="B站视频下载器", 
            fg=self.accent_color, bg=self.card_color,
            font=('Microsoft YaHei', 16, 'bold')
        )
        title_label.pack(pady=(20, 15))
        
        url_frame = tk.Frame(card_frame, bg=self.card_color)
        url_frame.pack(fill=tk.X, padx=20, pady=10)
        
        tk.Label(url_frame, text="视频链接:", fg=self.text_color, bg=self.card_color, 
                font=('Microsoft YaHei', 10)).pack(anchor=tk.W)
        
        self.url_entry = ttk.Entry(url_frame, style='Normal.TEntry')
        self.url_entry.pack(fill=tk.X, pady=(5, 0), ipady=3)
        
        btn_group = tk.Frame(url_frame, bg=self.card_color)
        btn_group.pack(side=tk.TOP, pady=10)
        
        self.parse_btn = ttk.Button(
            btn_group, text="解析链接", 
            command=self.parse_url,
            style='Action.TButton'
        )
        self.parse_btn.pack(side=tk.LEFT, padx=5)

        self.video_btn = ttk.Button(
            btn_group, text="下载视频", 
            command=lambda: self.download(True),
            style='Action.TButton'
        )
        self.video_btn.pack(side=tk.LEFT, padx=5)
        self.video_btn.config(state='disabled')
        
        self.audio_btn = ttk.Button(
            btn_group, text="下载音频", 
             command=lambda: self.download(False),
            style='Action.TButton'
        )
        self.audio_btn.pack(side=tk.LEFT, padx=5)
        self.audio_btn.config(state='disabled')

        self.show_list_btn = ttk.Button(
            btn_group, text="合集/分P列表", 
             command=lambda: self.show_list_page(),
            style='Action.TButton'
        )
        self.show_list_btn.pack(side=tk.LEFT, padx=5)
        self.show_list_btn.config(state='disabled')
        
        info_frame = tk.Frame(card_frame, bg=self.card_color, bd=0, relief='flat')
        info_frame.pack(fill=tk.X, padx=20, pady=10)
        
        info_title = tk.Label(info_frame, text="视频信息", fg=self.accent_color, bg=self.card_color,
                             font=('Microsoft YaHei', 12, 'bold'))
        info_title.pack(anchor=tk.W, pady=(0, 10))
        
        title_info_frame = tk.Frame(info_frame, bg=self.card_color)
        title_info_frame.pack(fill=tk.X, pady=5)
        
        tk.Label(title_info_frame, text="视频标题:", fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT)
        self.title_var = tk.StringVar()
        tk.Label(title_info_frame, textvariable=self.title_var, fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT, padx=(5, 0))
        
        size_info_frame = tk.Frame(info_frame, bg=self.card_color)
        size_info_frame.pack(fill=tk.X, pady=5)
        
        tk.Label(size_info_frame, text="文件大小:", fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT)
        self.size_var = tk.StringVar()
        tk.Label(size_info_frame, textvariable=self.size_var, fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT, padx=(5, 0))
        
        dpi_info_frame = tk.Frame(info_frame, bg=self.card_color)
        dpi_info_frame.pack(fill=tk.X, pady=5)
        
        tk.Label(dpi_info_frame, text="视频分辨率:", fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT)
        self.dpi_var = tk.StringVar()
        tk.Label(dpi_info_frame, textvariable=self.dpi_var, fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT, padx=(5, 0))
        
        settings_frame = tk.Frame(card_frame, bg=self.card_color, bd=0, relief='flat')
        settings_frame.pack(fill=tk.X, padx=20, pady=10)
        
        settings_title = tk.Label(settings_frame, text="下载设置", fg=self.accent_color, bg=self.card_color,
                                 font=('Microsoft YaHei', 12, 'bold'))
        settings_title.pack(anchor=tk.W, pady=(0, 10))
        
        path_frame = tk.Frame(settings_frame, bg=self.card_color)
        path_frame.pack(fill=tk.X, pady=5)
        
        tk.Label(path_frame, text="保存路径:", fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT)
        
        self.path_var = tk.StringVar()
        self.path_var.set(os.path.join(os.getcwd(), 'downloads'))
        
        path_entry = ttk.Entry(path_frame, textvariable=self.path_var, style='Normal.TEntry')
        path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 5), ipady=3)
        
        browse_btn = ttk.Button(path_frame, text="浏览", command=self.browse_path, style='Secondary.TButton')
        browse_btn.pack(side=tk.RIGHT)
        
        name_frame = tk.Frame(settings_frame, bg=self.card_color)
        name_frame.pack(fill=tk.X, pady=5)
        
        tk.Label(name_frame, text="文件名:   ", fg=self.text_color, bg=self.card_color,
                font=('Microsoft YaHei', 9)).pack(side=tk.LEFT)
        
        self.name_var = tk.StringVar()
        name_entry = ttk.Entry(name_frame, textvariable=self.name_var, style='Normal.TEntry')
        name_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0), ipady=3)
    
    def setup_tasks_page(self):
        card_frame = tk.Frame(self.tasks_page, bg=self.card_color, bd=0, relief='flat')
        card_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        title_label = tk.Label(
            card_frame, 
            text="下载任务", 
            fg=self.accent_color, bg=self.card_color,
            font=('Microsoft YaHei', 16, 'bold')
        )
        title_label.pack(pady=(20, 15))
        
        list_frame = tk.Frame(card_frame, bg=self.card_color)
        list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
        
        columns = ("name", "type", "progress", "status")
        self.tasks_tree = ttk.Treeview(
            list_frame, columns=columns, 
            show="headings", height=10
        )
        
        self.tasks_tree.heading("name", text="任务名称")
        self.tasks_tree.heading("type", text="下载类型")
        self.tasks_tree.heading("progress", text="进度")
        self.tasks_tree.heading("status", text="状态")
        
        self.tasks_tree.column("name", width=300)
        self.tasks_tree.column("type", width=100)
        self.tasks_tree.column("progress", width=100)
        self.tasks_tree.column("status", width=100)
        
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tasks_tree.yview)
        self.tasks_tree.configure(yscrollcommand=scrollbar.set)
        
        self.tasks_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        control_frame = tk.Frame(card_frame, bg=self.card_color)
        control_frame.pack(pady=(0, 20))
        
        ttk.Button(control_frame, text="暂停/继续", command=self.manage_task, style='Secondary.TButton').pack(side=tk.LEFT, padx=5)
        ttk.Button(control_frame, text="删除任务" , command=self.delete_task, style='Secondary.TButton').pack(side=tk.LEFT, padx=5)

    def setup_login_page(self):
        card_frame = tk.Frame(self.login_page, bg=self.card_color, bd=0, relief='flat')
        card_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        title_label = tk.Label(
            card_frame, 
            text="B站账户登录", 
            fg=self.accent_color, bg=self.card_color,
            font=('Microsoft YaHei', 16, 'bold')
        )
        title_label.pack(pady=(20, 15))
        
        # 警告信息
        warning_frame = tk.Frame(card_frame, bg=self.card_color)
        warning_frame.pack(fill=tk.X, padx=20, pady=10)
        
        warning_text = "⚠️ 请注意： 登录功能需要使用B站手机APP扫描二维码\n登录成功后，账户凭证Cookies将保存在本地，用于下载更高分辨率的视频"
        warning_label = tk.Label(warning_frame, text=warning_text, 
                               fg=self.warning_color, bg=self.card_color,
                               font=('Microsoft YaHei', 10), justify=tk.LEFT)
        warning_label.pack(anchor=tk.W)
        
        # 二维码显示区域
        qr_frame = tk.Frame(card_frame, bg=self.card_color)
        qr_frame.pack(pady=20)
        
        self.qr_label = tk.Label(qr_frame, bg=self.card_color)
        self.qr_label.pack()
        
        # 状态信息
        self.login_status = tk.StringVar()
        self.login_status.set("请使用B站APP扫描二维码登录")
        status_label = tk.Label(card_frame, textvariable=self.login_status,
                              fg=self.text_color, bg=self.card_color,
                              font=('Microsoft YaHei', 10))
        status_label.pack(pady=10)
        
        # 按钮区域
        btn_frame = tk.Frame(card_frame, bg=self.card_color)
        btn_frame.pack(pady=10)
        
        self.login_refresh_btn = ttk.Button(btn_frame, text="刷新二维码", command=lambda: threading.Thread(target=self.generate_qrcode, daemon=True).start(),
                  style='Action.TButton')
        self.login_refresh_btn.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(btn_frame, text="返回首页", command=self.show_download_page,
                  style='Secondary.TButton').pack(side=tk.LEFT, padx=5)
        
        # 初始化生成二维码
        self.generate_qrcode()
    
    def generate_qrcode(self):
        try:
            self.login_status.set("正在生成二维码...")
            self.login_refresh_btn.config(state=tk.DISABLED)

            url = 'https://passport.bilibili.com/x/passport-login/web/qrcode/generate?source=main-fe-header'
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0',
                'Referer': 'https://www.bilibili.com/',
                'Origin': 'https://www.bilibili.com'
            }
            
            response = requests.get(url=url, headers=headers).json()
            qrcode_url = response['data']['url']
            self.qrcode_key = response['data']['qrcode_key']
            
            # 生成二维码图片
            qr = QRCode()
            qr.add_data(qrcode_url)
            img = qr.make_image()
            img = img.resize((200, 200), Image.Resampling.LANCZOS)
            
            qr_img = ImageTk.PhotoImage(img)
            self.qr_label.config(image=qr_img)
            self.qr_label.image = qr_img
            
            # 启动登录检查线程
            self.session = requests.Session()
            if hasattr(self, 'login_thread'):
                self.login_thread.destory()
            
            self.login_thread = CheckLogin(self.qrcode_key, self.session, self.update_login_status)
            self.login_thread.daemon = True
            self.login_thread.start()
            self.login_status.set("请使用B站APP扫描二维码登录")
        except Exception as e:
            self.login_status.set(f"生成二维码失败，请检查网络连接或稍后重试")
            messagebox.showerror("错误", f"生成二维码失败: {e}")
        self.login_refresh_btn.config(state=tk.NORMAL)
    
    def update_login_status(self, success, message):
        if success is True:
            self.login_status.set(message)
            # 登录成功后重新加载用户信息
            self.update_user_info()
            messagebox.showinfo("登录成功", "登录成功！")
        elif success is False:
            self.login_status.set(message)
        else:
            self.login_status.set(message)
    
    def setup_list_page(self):
        card_frame = tk.Frame(self.list_page, bg=self.card_color, bd=0, relief='flat')
        card_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        title_label = tk.Label(
            card_frame, 
            text="视频合集列表", 
            fg=self.accent_color, bg=self.card_color,
            font=('Microsoft YaHei', 16, 'bold')
        )
        title_label.pack(pady=(20, 15))
        
        v_scrollbar = ttk.Scrollbar(card_frame)
        v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 创建树形视图
        self.tree_list = CheckboxTreeview(card_frame, show="tree headings", height=15)
        self.tree_list.pack(fill=tk.BOTH, expand=True)

        v_scrollbar.config(command=self.tree_list.yview)
        self.tree_list.config(yscrollcommand=v_scrollbar.set)

        # 按钮区域
        btn_frame = tk.Frame(card_frame, bg=self.card_color)
        btn_frame.pack(pady=10)
        
        ttk.Button(btn_frame, text="返回首页", command=self.show_download_page,
                  style='Secondary.TButton').pack(side=tk.LEFT, padx=5)
        
        ttk.Button(btn_frame, text="全选/取消全选", command=lambda: threading.Thread(target=self.toggle_select_all, daemon=True).start(),
                  style='Action.TButton').pack(side=tk.LEFT, padx=5)
        
        ttk.Button(btn_frame, text="下载选中的视频", command=lambda: threading.Thread(target=self.download_selected, args=(True,), daemon=True).start(),
                  style='Action.TButton').pack(side=tk.LEFT, padx=5)
        
        ttk.Button(btn_frame, text="下载选中的音频", command=lambda: threading.Thread(target=self.download_selected, args=(False,), daemon=True).start(),
                  style='Action.TButton').pack(side=tk.LEFT, padx=5)
    
    def toggle_select_all(self):
        """切换全选/取消全选状态"""
        # 检查当前是否有选中的项目
        any_selected = any(self.tree_list.checkbox_states.values())
        
        # 如果没有任何项目被选中，则全选；否则全不选
        new_state = not any_selected
        
        # 递归设置所有项目的状态
        def set_all_state(items, state):
            for item in items:
                self.tree_list.checkbox_states[item] = state
                self.tree_list.set(item, "checkbox", self.tree_list.checkbox_list[2] if state else self.tree_list.checkbox_list[0])
                # 递归处理子项目
                children = self.tree_list.get_children(item)
                if children:
                    set_all_state(children, state)
        
        # 从根项目开始设置
        root_items = self.tree_list.get_children("")
        set_all_state(root_items, new_state)
        
        # 更新父项目的状态（处理部分选中状态）
        for item in root_items:
            if  self.tree_list.get_children(item):
                self.tree_list.update_parent_state(item)

    def download_selected(self, isvideo:bool=True):
        selected_items = []
        for item, checked in self.tree_list.checkbox_states.items():
            if checked and not self.tree_list.get_children(item):
                selected_items.append(item)
        if not  selected_items:
            messagebox.showwarning("警告", "请先选择一个{}".format("视频" if isvideo else "音频"))
            return
        
        for item in selected_items:
            path = self.path_var.get()+self.tree_list.item(item, "values")[1] if self.path_var.get()[-1] == '\\' else self.path_var.get()+'\\'+self.tree_list.item(item, "values")[1]
            name = self.tree_list.item(item, "values")[2]
            if not os.path.exists(path):
                os.makedirs (path)
            self.create_task(path=path, name=name, is_video=isvideo, info='https://www.bilibili.com/video/'+item)
        self.show_tasks_page()

    def show_download_page(self):
        self.notebook.select(self.download_page)
    
    def show_tasks_page(self):
        self.notebook.select(self.tasks_page)

    def show_login_page(self):
        self.notebook.select(self.login_page)

    def show_list_page(self):
        self.notebook.select(self.list_page)
    
    def parse_url(self):
        url = self.url_entry.get()
        if not url:
            messagebox.showerror("错误", "请输入视频链接")
            return
        
        def parse_thread():
            try:
                result = self.downloader.getVideoInfo(url)
                if result['state']:
                    self.root.after(0, self.update_video_info, result)
                else:
                    self.root.after(0, messagebox.showerror, "错误", f"解析失败: {result['info']}")
            except Exception as e:
                self.root.after(0, messagebox.showerror, "错误", f"解析失败: 未知错误")
        
        threading.Thread(target=parse_thread, daemon=True).start()
    
    def stats_update(self):
        total_tasks = len(self.tasks_tree.get_children()) if self.tasks_tree else 0
        completed_tasks = sum(1 for item in self.tasks_tree.get_children() 
                            if self.tasks_tree.item(item, "values")[3] == "已完成") if self.tasks_tree else 0
        
        stats_text = f"总任务: {total_tasks}\n已完成: {completed_tasks}"
        self.stats_label.config(text=stats_text)
        self.root.after(500, self.stats_update)

    def update_video_info(self, info):
        def safe_filename(filename):
            return re.sub(r'[\\/:*?"<>|“‘’”？。…]', '', filename)
        self.title_var.set(info['title'])
        self.name_var.set(safe_filename(info['title']))
        self.size_var.set(f"{info['size'][0] / 1024 / 1024:.2f}MB (视频) + {info['size'][1] / 1024 / 1024:.2f}MB (音频)")
        self.dpi_var.set(f"{info['dpi'][0]}x{info['dpi'][1]}")
    
    def browse_path(self):
        path = filedialog.askdirectory()
        if  path:
            self.path_var.set(path.replace('/', '\\') if os.name == 'nt' else path)
    
    def download(self, is_video):
        path = self.path_var.get()
        name = self.name_var.get()

        if not path:messagebox.showerror("错误", "请选择保存路径");return
        if not name:messagebox.showerror("错误", "请输入文件名"  );return
        if len(name) > 255:messagebox.showerror("错误", "文件名长度不能超过255个字符");return
        if name[0]  == ' ':messagebox.showerror("错误", "文件名不能以空格开头"       );return
        if re.search(r'[\\/:*?"<>|]', name) is not None:
            messagebox.showerror("错误", "文件名不能包含以下字符: \\ / : * ? \" < > |")
            return
        if os.path.exists(os.path.join(path, f"{name}.mp4" if is_video else f"{name}.mp3")):
            if os.path.isdir(os.path.join(path, f"{name}.mp4" if is_video else f"{name}.mp3")):
                messagebox.showerror("错误", "文件名与目录名冲突")
                return
            if not messagebox.askyesno("提示", "{} 已存在，是否覆盖？".format(f"{name}.mp4" if is_video else f"{name}.mp3")):return

        self.create_task(path, name, is_video);self.show_tasks_page()

    def create_task(self, path:str, name:str, is_video:bool, info=None):
        if not path or not name:return
        
        task_id   = str(self.tasks_CMID);self.tasks_CMID += 1
        task_type = "视频" if is_video else "音频"
        self.tasks_tree.insert("", "end", task_id, 
                              values=(name, task_type, "0%", "等待中"))
        def download_thread():
            self.downloader.download(
                path=path,
                info=self.downloader.current_info if info is None else info,
                file_name=name,
                only_audio=not is_video,
                progressFunc=self.update_progress,
                taskID=task_id
            )
        self.tasks_thread.update({task_id: threading.Thread(target=download_thread, daemon=True)})

    def download_manager(self):
        maximum_threads = 3
        current_threads = 0
        try:
            for item in self.tasks_tree.get_children():
                if  self.tasks_tree.item(item, "values")[3] == "下载中" or \
                    self.tasks_tree.item(item, "values")[3] == "准备中":
                    current_threads += 1
                if  current_threads >= maximum_threads:break
                if  self.tasks_tree.item(item, "values")[3] == "等待中":
                    self.tasks_thread[item].start()
                    current_threads += 1
                if  current_threads >= maximum_threads:break
        except:pass
        self.root.after(200, self.download_manager)
    
    def update_progress(self, total, current, taskID, success:bool=False, info=None):
        if  not self.tasks_tree.exists(taskID):
            return
        if  not success and info is None:
            progress = f"{current / total * 100:.1f}%"
            self.tasks_tree.set(taskID, "progress", progress)
            if  self.tasks_tree.item(taskID, "values")[3] != "暂停中":
                self.tasks_tree.set (taskID, "status", "下载中" if current < total else "文件转换中")
        elif success:
            self.tasks_tree.set(taskID, "progress", "100%")
            self.tasks_tree.set(taskID, "status", "已完成")
        else:
            self.tasks_tree.set(taskID, "status", f"失败: {info}")
    
    def manage_task(self):
        selected = self.tasks_tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择一个任务")
            return
        
        task_id = selected[0]
        status = self.tasks_tree.item(task_id, "values")[3]
        
        if status == "暂停中":
            self.tasks_tree.set(task_id, "status", "下载中")
        elif status == "下载中":
            self.tasks_tree.set(task_id, "status", "暂停中")
        else:
            messagebox.showinfo("提示", "只能暂停或继续正在下载的任务")
    
    def delete_task(self):
        selected = self.tasks_tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择一个任务")
            return
        
        for task_id in selected:
            self.tasks_tree.delete(task_id)

if __name__ == "__main__":
    root = tk.Tk()
    app  = BiliBiliDownloader(root)
    try   :root.mainloop()
    except:pass